#include "foobar2000.h"

void audio_chunk::set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config)
{
    t_size size = samples * nch;
    set_data_size(size);
    if (src)
        pfc::memcpy_t(get_data(),src,size);
    else
        pfc::memset_t(get_data(),(audio_sample)0,size);
    set_sample_count(samples);
    set_channels(nch,channel_config);
    set_srate(srate);
}

static bool check_exclusive(unsigned val, unsigned mask)
{
    return (val&mask)!=0 && (val&mask)!=mask;
}

namespace
{

    template<class T,bool b_swap,bool b_signed,bool b_pad> class msvc6_sucks_v2
    {
    public:
        inline static void do_fixedpoint_convert(const void * source,unsigned bps,t_size count,audio_sample* buffer)
        {
            const char * src = (const char *) source;
            unsigned bytes = bps>>3;
            t_size n;
            T max = ((T)1)<<(bps-1);

            T negmask = - max;

            ASSUME(bytes<=sizeof(T));

            const double div = 1.0 / (double)(1<<(bps-1));
            for (n=0;n<count;n++)
            {
                T temp;
                if (b_pad)
                {
                    temp = 0;
                    memcpy(&temp,src,bytes);
                    if (b_swap) pfc::byteswap_raw(&temp,bytes);
                }
                else
                {
                    temp = * reinterpret_cast<const T*>(src);
                    if (b_swap) temp = pfc::byteswap_t(temp);
                }



                if (!b_signed) temp ^= max;

                if (b_pad)
                {
                    if (temp & max) temp |= negmask;
                }

                if (b_pad)
                    src += bytes;
                else
                    src += sizeof(T);


                buffer[n] = (audio_sample) ( (double)temp * div );
            }
        }
    };

    template <class T,bool b_pad> class msvc6_sucks
    {
    public:
        inline static void do_fixedpoint_convert(bool b_swap,bool b_signed,const void * source,unsigned bps,t_size count,audio_sample* buffer)
        {
            if (sizeof(T)==1)
            {
                if (b_signed)
                {
                    msvc6_sucks_v2<T,false,true,b_pad>::do_fixedpoint_convert(source,bps,count,buffer);
                }
                else
                {
                    msvc6_sucks_v2<T,false,false,b_pad>::do_fixedpoint_convert(source,bps,count,buffer);
                }
            }
            else if (b_swap)
            {
                if (b_signed)
                {
                    msvc6_sucks_v2<T,true,true,b_pad>::do_fixedpoint_convert(source,bps,count,buffer);
                }
                else
                {
                    msvc6_sucks_v2<T,true,false,b_pad>::do_fixedpoint_convert(source,bps,count,buffer);
                }
            }
            else
            {
                if (b_signed)
                {
                    msvc6_sucks_v2<T,false,true,b_pad>::do_fixedpoint_convert(source,bps,count,buffer);
                }
                else
                {
                    msvc6_sucks_v2<T,false,false,b_pad>::do_fixedpoint_convert(source,bps,count,buffer);
                }
            }
        }
    };


};


void audio_chunk::set_data_fixedpoint_ex(const void * source,t_size size,unsigned srate,unsigned nch,unsigned bps,unsigned flags,unsigned p_channel_config)
{
    assert( check_exclusive(flags,FLAG_SIGNED|FLAG_UNSIGNED) );
    assert( check_exclusive(flags,FLAG_LITTLE_ENDIAN|FLAG_BIG_ENDIAN) );

    bool need_swap = !!(flags & FLAG_BIG_ENDIAN);
    if (pfc::byte_order_is_big_endian) need_swap = !need_swap;

    t_size count = size / (bps/8);
    set_data_size(count);
    audio_sample * buffer = get_data();
    bool b_signed = !!(flags & FLAG_SIGNED);

    switch (bps)
    {
    case 8:
        msvc6_sucks<t_int8,false>::do_fixedpoint_convert(need_swap,b_signed,source,bps,count,buffer);
        break;
    case 16:
        if (!need_swap && b_signed) audio_math::convert_from_int16((const t_int16*)source,count,buffer,1.0);
        else msvc6_sucks<t_int16,false>::do_fixedpoint_convert(need_swap,b_signed,source,bps,count,buffer);
        break;
    case 24:
        msvc6_sucks<t_int32,true>::do_fixedpoint_convert(need_swap,b_signed,source,bps,count,buffer);
        break;
    case 32:
        if (!need_swap && b_signed) audio_math::convert_from_int32((const t_int32*)source,count,buffer,1.0);
        else msvc6_sucks<t_int32,false>::do_fixedpoint_convert(need_swap,b_signed,source,bps,count,buffer);
        break;
    default:
        //unknown size, cant convert
        pfc::memset_t(buffer,(audio_sample)0,count);
        break;
    }
    set_sample_count(count/nch);
    set_srate(srate);
    set_channels(nch,p_channel_config);
}

template<class t_float>
static void process_float_multi(audio_sample * p_out,const t_float * p_in,const t_size p_count)
{
    t_size n;
    for (n=0;n<p_count;n++)
        p_out[n] = (audio_sample)p_in[n];
}

template<class t_float>
static void process_float_multi_swap(audio_sample * p_out,const t_float * p_in,const t_size p_count)
{
    t_size n;
    for (n=0;n<p_count;n++)
    {
        p_out[n] = (audio_sample) pfc::byteswap_t(p_in[n]);
    }
}

void audio_chunk::set_data_floatingpoint_ex(const void * ptr,t_size size,unsigned srate,unsigned nch,unsigned bps,unsigned flags,unsigned p_channel_config)
{
    assert(bps==32 || bps==64);
    assert( check_exclusive(flags,FLAG_LITTLE_ENDIAN|FLAG_BIG_ENDIAN) );
    assert( ! (flags & (FLAG_SIGNED|FLAG_UNSIGNED) ) );

    bool use_swap = pfc::byte_order_is_big_endian ? !!(flags & FLAG_LITTLE_ENDIAN) : !!(flags & FLAG_BIG_ENDIAN);

    const t_size count = size / (bps/8);
    set_data_size(count);
    audio_sample * out = get_data();

    if (bps == 32)
    {
        if (use_swap)
            process_float_multi_swap(out,reinterpret_cast<const float*>(ptr),count);
        else
            process_float_multi(out,reinterpret_cast<const float*>(ptr),count);
    }
    else if (bps == 64)
    {
        if (use_swap)
            process_float_multi_swap(out,reinterpret_cast<const double*>(ptr),count);
        else
            process_float_multi(out,reinterpret_cast<const double*>(ptr),count);
    }
    else throw exception_io_data("invalid bit depth");

    set_sample_count(count/nch);
    set_srate(srate);
    set_channels(nch,p_channel_config);
}

bool audio_chunk::is_valid() const
{
    unsigned nch = get_channels();
    if (nch==0 || nch>256) return false;
    if (!g_is_valid_sample_rate(get_srate())) return false;
    t_size samples = get_sample_count();
    if (samples==0 || samples >= 0x80000000 / (sizeof(audio_sample) * nch) ) return false;
    t_size size = get_data_size();
    if (samples * nch > size) return false;
    if (!get_data()) return false;
    return true;
}


void audio_chunk::pad_with_silence_ex(t_size samples,unsigned hint_nch,unsigned hint_srate)
{
    if (is_empty())
    {
        if (hint_srate && hint_nch)
        {
            return set_data(0,samples,hint_nch,hint_srate);
        }
        else throw exception_io_data();
    }
    else
    {
        if (hint_srate && hint_srate != get_srate()) samples = MulDiv_Size(samples,get_srate(),hint_srate);
        if (samples > get_sample_count())
        {
            t_size old_size = get_sample_count() * get_channels();
            t_size new_size = samples * get_channels();
            set_data_size(new_size);
            pfc::memset_t(get_data() + old_size,(audio_sample)0,new_size - old_size);
            set_sample_count(samples);
        }
    }
}

void audio_chunk::pad_with_silence(t_size samples)
{
    if (samples > get_sample_count())
    {
        t_size old_size = get_sample_count() * get_channels();
        t_size new_size = samples * get_channels();
        set_data_size(new_size);
        pfc::memset_t(get_data() + old_size,(audio_sample)0,new_size - old_size);
        set_sample_count(samples);
    }
}

void audio_chunk::insert_silence_fromstart(t_size samples)
{
    t_size old_size = get_sample_count() * get_channels();
    t_size delta = samples * get_channels();
    t_size new_size = old_size + delta;
    set_data_size(new_size);
    audio_sample * ptr = get_data();
    pfc::memmove_t(ptr+delta,ptr,old_size);
    pfc::memset_t(ptr,(audio_sample)0,delta);
    set_sample_count(get_sample_count() + samples);
}

t_size audio_chunk::skip_first_samples(t_size samples_delta)
{
    t_size samples_old = get_sample_count();
    if (samples_delta >= samples_old)
    {
        set_sample_count(0);
        set_data_size(0);
        return samples_old;
    }
    else
    {
        t_size samples_new = samples_old - samples_delta;
        unsigned nch = get_channels();
        audio_sample * ptr = get_data();
        pfc::memmove_t(ptr,ptr+nch*samples_delta,nch*samples_new);
        set_sample_count(samples_new);
        set_data_size(nch*samples_new);
        return samples_delta;
    }
}

audio_sample audio_chunk::get_peak(audio_sample p_peak) const
{
    return pfc::max_t(p_peak, get_peak());
}

audio_sample audio_chunk::get_peak() const
{
    return audio_math::calculate_peak(get_data(),get_sample_count() * get_channels());
}

void audio_chunk::scale(audio_sample p_value)
{
    audio_sample * ptr = get_data();
    audio_math::scale(ptr,get_sample_count() * get_channels(),ptr,p_value);
}
